Shell 循环语句
for 循环
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
写成一行:
for var in item1 item2 ... itemN; do command1; command2… done;
当变量值在列表里,for 循环即执行一次所有命令,使用变量名获取列表中的当前取值。命令可为任何有效的 shell 命令和语句。in 列表可以包含替换、字符串和文件名。
in列表是可选的,如果不用它,for循环使用命令行的位置参数。
例如,顺序输出当前列表中的数字:
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
顺序输出字符串中的字符:
#!/bin/bash
for str in This is a string
do
echo $str
done
输出结果:
This
is
a
string
从变量读取列表
#!/bin/bash
# using a variable to hold the list
list="Alabama Alaska Arizona Arkansas Colorado"
# 给这个 list 添加一个 Connecticut 变量
list=$list" Connecticut"
for state in $list
do
echo "Have you ever visited $state?"
done
输出:
Have you ever visited Alabama?
Have you ever visited Alaska?
Have you ever visited Arizona?
Have you ever visited Arkansas?
Have you ever visited Colorado?
Have you ever visited Connecticut?
遍历数组
## declare an array variable
declare -a arr=("element1" "element2" "element3")
## now loop through the above array
for i in "${arr[@]}"
do
echo "$i"
# or do whatever with individual element of the array
done
# You can access them using echo "${arr[0]}", "${arr[1]}" also
从命令读取值
有时需要从命令里面读取返回值来循环
如下:
创建一个文件 test.txt
Alabama Alaska Arizona Arkansas
Colorado
Connecticut
Delaware
Florida
Georgia
从这个文件里面读取
#!/bin/bash
# reading values from a file
file="test.txt"
for state in $(cat $file)
do
echo "Visit beautiful $state"
done
输出:
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
Visit beautiful Arkansas
Visit beautiful Colorado
Visit beautiful Connecticut
Visit beautiful Delaware
Visit beautiful Florida
Visit beautiful Georgia
更改字段分隔符
但是仔细观察可以发现,上面不管是空格还是换行符,在循环中都当一个新的元素打印出来,造成这个问题的原因是特殊的环境变量 IFS,叫作内部字段分隔符(internal field separator)。IFS 环境变量定义了 bash shell 用作字段分隔符的一系列字符。默认情况下,bash shell 会将下列字符当作字段分隔符:
- 空格
- 制表符
- 换行符
如果 bash shell 在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。在处理可能含有空格的数据(比如文件名)时,这会非常麻烦,就像上面脚本示例中看到的。
要解决这个问题,可以在 shell 脚本中临时更改 IFS 环境变量的值来限制被 bash shell 当作字段分隔符的字符。
IFS=$'\n'
将这个语句加入到脚本中,告诉 bash shell 在数据值中忽略空格和制表符。
在处理代码量较大的脚本时,可能在一个地方需要修改 IFS 的值,然后忽略这次修改,在脚本的其他地方继续沿用 IFS 的默认值。一个可参考的安全实践是在改变 IFS 之前保存原来的 IFS 值,之后再恢复它。这就保证了在脚本的后续操作中使用的是 IFS 的默认值。
上面的脚本更新为:
#!/bin/bash
IFS_OLD=$IFS
IFS=$'\n'
# reading values from a file
file="test.txt"
for state in $(cat $file)
do
echo "Visit beautiful $state"
done
# 变更回来
IFS=$IFS_OLD
# do somthing...
输出:
Visit beautiful Alabama Alaska Arizona Arkansas
Visit beautiful Colorado
Visit beautiful Connecticut
Visit beautiful Delaware
Visit beautiful Florida
Visit beautiful Georgia
还有其他一些 IFS 环境变量的绝妙用法。假定你要遍历一个文件中用冒号分隔的值(比如在/etc/passwd 文件中)。你要做的就是将 IFS 的值设为冒号
IFS=:
如果要指定多个 IFS 字符,只要将它们在赋值行串起来就行。
IFS=$'\n':;"
这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。如何使用 IFS 字符解析数据没有任何限制。
用通配符读取目录
可以用 for 命令来自动遍历目录中的文件。进行此操作时,必须在文件名或路径名中使用通配符。它会强制 shell 使用文件扩展匹配。文件扩展匹配是生成匹配指定通配符的文件名或路径名的过程。
#!/bin/bash
for file in "$HOME"/Documents/markdownNote/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
输出:
/home/alsritter/Documents/markdownNote/分布式的理论和算法 is a directory
/home/alsritter/Documents/markdownNote/前端 is a directory
/home/alsritter/Documents/markdownNote/图形学 is a directory
/home/alsritter/Documents/markdownNote/学习计划(随时更新).md is a file
注意,在这个例子的 if 语句中做了一些不同的处理
if [ -d "$file" ]
在 Linux 中,目录名和文件名中包含空格当然是合法的。要适应这种情况,应该将 $file
变量用双引号圈起来。如果不这么做,遇到含有空格的目录名或文件名时就会有错误产生。
./test.sh: line 6: [: too many arguments
./test.sh: line 9: [: too many arguments
也可以在 for 命令中列出多个目录通配符,将目录查找和列表合并进同一个 for 语句。
#!/bin/bash
for file in "$HOME"/Documents/markdownNote/* "$HOME"/Desktop
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
C 语言风格的 for 命令
#!/bin/bash
# testing the C-style for loop
for (( i=1; i <= 3; i++ ))
do
echo "The next number is $i"
done
C 语言风格的 for 命令也允许为迭代使用多个变量。循环会单独处理每个变量
#!/bin/bash
# multiple variables
for (( a=1, b=10; a <= 3; a++, b-- ))
do
echo "$a - $b"
done
循环输出
在 shell 脚本中,你可以对循环的输出使用管道或进行重定向。这可以通过在 done 命令之后添加一个处理命令来实现。
for file in /home/alsritter/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
else
echo "$file is a file"
fi
done > output.txt
执行后可以发现,它把输出重定向到了 output.txt
文件里面了,同理可以使用管道符
# ...
done | sort
while 语句
while condition
do
command
done
以下是一个基本的 while 循环,测试条件是:如果 int 小于等于 5,那么条件返回真。int 从 1 开始,每次循环处理时,int 加 1。运行上述脚本,返回数字 1 到 5,然后终止。
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
跳出循环
break命令
#!/bin/bash
while :
do
echo -n "输入 1 到 5 之间的数字:"
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;;
*) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
break
;;
esac
done
有时你在内部循环,但需要停止外部循环。break 命令接受单个命令行参数值:
break n
其中 n 指定了要跳出的循环层级。默认情况下,n 为 1,表明跳出的是当前的循环。如果你将 n 设为 2,break 命令就会停止下一级的外部循环。
e.g.
#!/bin/bash
# breaking out of an outer loop
for (( a = 1; a < 4; a++ ))
do
echo "Outer loop: $a"
for (( b = 1; b < 100; b++ ))
do
if [ $b -gt 4 ]
then
break 2
fi
echo " Inner loop: $b"
done
done
$ ./test.sh
Outer loop: 1
Inner loop: 1
Inner loop: 2
Inner loop: 3
Inner loop: 4
$
continue
#!/bin/bash
while :
do
echo -n "输入 1 到 5 之间的数字: "
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;;
*) echo "你输入的数字不是 1 到 5 之间的!"
continue
echo "游戏结束"
;;
esac
done
和 break 命令一样,continue 命令也允许通过命令行参数指定要继续执行哪一级循环:
continue n
其中 n 定义了要继续的循环层级。
使用例子
查找可执行文件
如果你想找出系统中有哪些可执行文件可供使用,只需要扫描 PATH 环境变量中所有的目录就行了。
首先是创建一个 for 循环,对环境变量 PATH 中的目录进行迭代。处理的时候别忘了设置 IFS 分隔符。
IFS=:
for folder in $PATH
do
现在已经将各个目录存放在了变量 $folder
中,可以使用另一个 for 循环来迭代特定目录中的所有文件。
for file in $folder/*
do
最后一步是检查各个文件是否具有可执行权限,可以使用 if-then 测试功能来实现。
if [ -x $file ]
then
echo " $file"
fi
最后将这些代码片段组合成脚本就行了。
#!/bin/bash
# finding files in the PATH
IFS=:
for folder in $PATH
do
echo "$folder:"
for file in $folder/*
do
if [ -x $file ]
then
echo " $file"
fi
done
done